package io;

import java.io.File;
import java.io.IOException;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;

import control.Main;

/**
 * A generic audio player based on javax.sound <br>
 * Responsible for playing sound effects.<br>
 * Currently supports 16-bit sounds only.
 * TODO: enable 24-bit wave
 */
public class AudioPlay {

    //private boolean enabled = false;

    private static final int EXTERNAL_BUFFER_SIZE = 524288; // 128Kb

    //Panning
    enum Position {
        LEFT, RIGHT, NORMAL
    };

    /**
     * Enables/Disables the audio player. It is disabled in its initial state,
     * so this method has to be called once before any sound can be played.
     * @param onOFF true -> enabled, false -> disabled
     */
    public final void setEnabled(final Boolean onOFF) {
        Main.s.setSFXEnabled(onOFF);
    }

    /**
     * Creates a new thread that will play the specified sound.
     * It is advised to use the sounds from Constants.Sounds
     * @param file the path to the sound
     * @see constants.Constants.Sounds
     */
    public final void playSound(final String file) {
        if (Main.s.isSFXEnabled()) {
            new Player(file).start();
        }
    }

    //This is the thread that actually plays sounds
    private static class Player extends Thread {

        private String filename;
        private Position curPosition;

        public Player(final String file) {
            filename = file;
            curPosition = Position.NORMAL;
            setDaemon(true);
        }

        public void run() {

            File soundFile = new File(filename);

            if (!soundFile.exists()) {
                System.err.println("Wave file not found: " + filename);
                return;
            }

            //Get the data from the wave file
            AudioInputStream audioInputStream = null;
            try {
                audioInputStream = AudioSystem.getAudioInputStream(soundFile);
            } catch (UnsupportedAudioFileException e1) {
                e1.printStackTrace();
                return;
            } catch (IOException e1) {
                e1.printStackTrace();
                return;
            }

            AudioFormat format = audioInputStream.getFormat();
            SourceDataLine auline = null;
            DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);

            //Open a line that will play our audio stream
            try {
                auline = (SourceDataLine) AudioSystem.getLine(info);
                auline.open(format);
            } catch (LineUnavailableException e) {
                e.printStackTrace();
                return;
            } catch (Exception e) {
                e.printStackTrace();
                return;
            }

            //Set the panning
            if (auline.isControlSupported(FloatControl.Type.PAN)) {
                FloatControl pan = (FloatControl) auline
                .getControl(FloatControl.Type.PAN);
                if (curPosition == Position.RIGHT) {
                    pan.setValue(1.0f);
                } else if (curPosition == Position.LEFT) {
                    pan.setValue(-1.0f);
                }
            }

            //Allow the line to process data
            auline.start();

            int nBytesRead = 0;
            byte[] abData = new byte[EXTERNAL_BUFFER_SIZE];

            //Read all data from the input stream into the buffer and then to the line.
            try {
                while (nBytesRead != -1) {
                    nBytesRead = audioInputStream.read(abData, 0, abData.length);
                    if (nBytesRead >= 0) {
                        auline.write(abData, 0, nBytesRead);
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                return;
            } finally {
                auline.drain();
                auline.close();
            }
        }
    }
}
